package NetWork;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

// UDP 版本的 回显服务器 的实现
public class UdpEchoServer {
    // 网络编程, 本质上就是要操作网卡
    // 但是网卡不方便直接操作, 在操作系统内核中, 使用了一种特殊叫做 "socket" 这样的文件来抽象表示网卡
    // 因此进行网络通信势必现有一个 socket 对象
    private DatagramSocket socket = null;


    // 实例化对象
    // 对于服务器来说 创建一个 socket 对象的同时,要让它绑定上一个具体的端口号
    // 服务器一定要关联上一个具体的端口 !!!
    // 服务器是网络传输中,被动的一方.如果是操作系统随机分配的端口,此时客户端就不知道这个端口号是啥了,也就无法进行通信了!!
    // 所以要在服务器这里 构建服务器的时候把端口号确定下来
    public UdpEchoServer(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }

    // 启动服务器
    public void start() throws IOException {
        System.out.println("服务器启动!");
        // 服务器不是只给一个客户端服务就完了, 而是要给很多客户端
        // 所以要整一个 while 循环 (只要客户端过来我就提供服务)
        while (true){ // 会快速进行循环 , 每循环一次就处理一次 请求-响应
            // 1. 先读取客户端发来的请求是什么
            // (对于 UDP 来说 , 传输数据的基本单位是 DatagramPacket)
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096); // 里面需要有一个缓冲数组
            // 上面就是构造好的一个空的请求对象
            socket.receive(requestPacket); // receive 通过这个方法来读取请求, 参数是一个输出型参数, 需要先构造好一个 空白的一个DatagramPacket 对象, 交给 receive 来进行填充
            // receive 内部 会针对参数的对象进行填充数据, 填充数据来自网卡

            // 此时这个 DatagramPacket 是一个特殊的对象,并不方便直接进行处理, 可以把这里包含的数据拿出来,构造成一个字符串.
            String request = new String(requestPacket.getData(),0, requestPacket.getLength()); // requestPacket.getData() 得到里面的字节数组,
            // 把字节数组 取出来之后, 此处给的最大长度是 4096,但是这里的空间不一定用满了  所以 规定一个长度 0 ~ requestPacket.getLength() (构造字符串,哪些用了就构造哪一部分)
            // 这就把 DatagramPacket 对象转成一个字符串 (把实际有效的部分构造成一个字符串)

            // 2. 根据请求计算响应
            // 此处是回显服务器, 响应和请求相同
            String response = process(request);

            // 3. 把响应数据写回到客户端 (用到 send() ) send 的参数也是一个 DatagramPacket, 所以需要法这个Packet 对象构造好
            // 此处构造的这个响应对象 不能是空的字节数组来构造了, 而是需要使用响应数据response来构造
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
                    requestPacket.getSocketAddress() ); // getBytes 拿到字符串里面的字节数组
            // 同时还要获取长度 , (此处的长度 使用 response.length() 是否可以? 是不是必须写作 response,getBytes(),length 么?)
            // (这里面的区别就是 : 这两个操作的计量单位不同, 一个是计算字符的个数, 一个是计算字节的个数, 字符的个数是个字节的个数是有可能不一样的, 此处我们需要获取字节的个数.
            // DatagramPacket 这个不认识字符, 只认识字节 )
            //  requestPacket.getSocketAddress()  需要指定是谁发送的 , getSocketAddress 这里就是代表客户端的IP和端口号(这两个信息本事就在 requestPacket 中)
            // (这 IP 和 端口号, 就可以表示成 一个 有人来点餐了,点完之后,就给他一个小票, 然后看到他和他的朋友们,找了个地方坐下了, 做的地方就是 ip地址,
            // 小票上的编号就是客户端的端口号)
            socket.send(responsePacket); // 然后进行发送

            // 4. 打印一下,当前这次请求处理的中间结果
            System.out.printf("[%s,%d] req: %s; resp: %s\n",requestPacket.getAddress().toString(), responsePacket.getPort(), request,response);
            // requestPacket.getAddress().toString(),获取到 Packet 里面的 IP , responsePacket.getPort()获取到里面的端口
        }
        // 但是在每一次循环当中, 客户端什么时候发请求? 不知道
        // 会有多少个客户端来发请求? 还是不知道
        // 所以 循环的速度 是要和 请求的步调是一致的
        // 那么如何控制这个步调呢 ?  是由receive来进行控制的
        // 如果客户端发来请求了, receive 就能顺利读出来 (这个东西类似于 Scanner 的操作,读取控制台数据, 用户啥时候输入啥时候进行, 不输入就一直阻塞)
        // 如果客户端没有发送请求, 此时 receive 就会阻塞!

        // 那么假设有多个客户端,并且请求发的很快呢? 是否咱们这个while 循环就处理不过来了?? 太会有了, 当前所谓的"高并发"就是这个意思.
        // 就是 客户端请求发的太多了, 处理不过来了
        // 所以要进行特殊的处理 , 可以用多线程的 方式更充分的调度计算机的硬件资源...
        // 但是多线程是极限的, 所以这个时候可以多加几台机器,(当然管理成本也就提高了)
        // 这就是所谓的 分布式

        // 而且服务器中的while 就是一个死循环的, 服务器要 7*24小时运行的
    }

    // 这个方法就表示 根据请求来计算响应
    private String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        // 端口号可以随便 在 1024 -> 65535 这个范围里指定一个.
        UdpEchoServer server = new UdpEchoServer(9090);
        server.start();
        // 服务器的工作流程
        // 1. 读取请求并解析
        // 2. 根据请求计算响应
        // 3.构造响应并写回给客户端
    }
}
